/*
 * Copyright 2011 myTDev.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mytdev.commons.server;

import java.beans.ExceptionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 
 * @author Yann D'Isanto
 */
public class Server<T> {

    private static final Logger LOG = Logger.getLogger(Server.class.getName());
    private ServerEngine<T> serverEngine;
    private Consumer<T> consumer;
    private Thread listeningThread;
    private boolean doRun = false;
    private boolean running = false;
    private final List<ExceptionListener> listeners = Collections.synchronizedList(new ArrayList<ExceptionListener>());
    private final Runnable serverRunnable = new Runnable() {

        @Override
        public void run() {
            while (doRun) {
                try {
                    final T t = serverEngine.listen();
                    new Thread() {

                        @Override
                        public void run() {
                            consumer.consume(t);
                        }
                    }.start();
                } catch (TimeoutException ex) {
                    LOG.fine(ex.getLocalizedMessage());
                    LOG.log(Level.FINER, ex.getLocalizedMessage(), ex);
                } catch (IOException ex) {
                    fireExceptionThrown(ex);
                }
            }
            running = false;
        }
    };

    /**
     * Creates a new server using the specified server engine and the specified 
     * consumer.
     * @param serverEngine the server engine.
     * @param consumer the consumer.
     */
    public Server(ServerEngine<T> serverEngine, Consumer<T> consumer) {
        if(serverEngine == null) {
            throw new NullPointerException("server engine is null");
        }
        if(consumer == null) {
            throw new NullPointerException("consumer is null");
        }
        this.serverEngine = serverEngine;
        this.consumer = consumer;
    }

    /**
     * Starts this server if not already running.
     */
    public synchronized void start() {
        if (!running) {
            running = true;
            doRun = true;
            listeningThread = new Thread(serverRunnable);
            listeningThread.start();
        }
    }

    /**
     * Tells this server to stop. This method does not wait for this
     * server to actually stops. 
     */
    public void stop() {
        doRun = false;
    }

    /**
     * Returns true if this server is running, false otherwise.
     * @return true if this server is running, false otherwise. 
     */
    public boolean isRunning() {
        return running;
    }

    /**
     * Returns this server timeout.
     * @return this server timeout.
     * @throws IOException if an I/O error occurs.
     */
    public int getTimeout() throws IOException {
        return serverEngine.getTimeout();
    }

    /**
     * Sets the specified timeout. A timeout of zero is interpreted as an infinite timeout.
     * @param timeout the specified timeout, in milliseconds.
     * @throws IOException if an I/O error occurs.
     */
    public void setTimeout(int timeout) throws IOException {
        serverEngine.setTimeout(timeout);
    }

    /**
     * Reports a thrown exception.
     * @param ex the exception.
     */
    protected void fireExceptionThrown(Exception ex) {
        synchronized (listeners) {
            for (ExceptionListener listener : listeners) {
                listener.exceptionThrown(ex);
            }
        }
    }

    /**
     * Registers listener so that it will receive Exceptions thrown by this 
     * server when running.
     * @param listener the ExceptionListener to register
     */
    public void addExceptionListener(ExceptionListener listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }

    /**
     * Unregisters listener so that it will no longer receive Exceptions.
     * @param listener 
     */
    public void removeExceptionListener(ExceptionListener listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }
}
